fix: resolve GPS L1 week-rollover ambiguity via approximate_year#32
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #32 +/- ##
==========================================
+ Coverage 88.42% 88.57% +0.14%
==========================================
Files 4 4
Lines 242 245 +3
==========================================
+ Hits 214 217 +3
Misses 28 28 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Contributor
Benchmark Results (Julia v1)Time benchmarks
Memory benchmarks
|
The legacy GPS L1 C/A LNAV message (IS-GPS-200, §20.3.3.3) broadcasts
only a 10-bit week number — modulo 1024 — so the receiver cannot
determine which 1024-week cycle the recording is in from the broadcast
data alone. Each cycle is ~19.6 years, with rollover boundaries at
1999-08-22, 2019-04-07, 2038-11-21, and 2058-07-08.
The previous implementation hardcoded `2048 + decoder.data.trans_week`,
which works only for recordings inside the post-2019 cycle. Recordings
from earlier cycles produced PVT timestamps offset by N×19.6 years
(observed: ION RTL-SDR sample data from 2017-09-10 reported as
2037-04-26).
This change adds an `approximate_year::Integer` keyword to `calc_pvt`
(default `year(now(UTC))`) that selects the cycle whose absolute date
is closest to the anchor. Anything within ~9 years of the actual
observation date works.
Live receivers keep working with the default. Post-processing of
archived data passes the rough year of the recording; for example:
calc_pvt(states; approximate_year = 2017)
Pinning is also necessary on test fixtures that bake in expected TAI
timestamps — without it those tests would silently break in 2030+ when
the wall-clock-derived default starts picking the next cycle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zsoerenm
force-pushed
the
ss/fix-week-rollover
branch
from
May 7, 2026 16:57
f106854 to
e7e2191
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes incorrect PVT timestamps on legacy GPS L1 C/A recordings outside the current 1024-week cycle.
The LNAV message (IS-GPS-200, §20.3.3.3) broadcasts only a 10-bit week number, modulo 1024, so the receiver cannot determine the cycle from the broadcast data alone. Cycle boundaries: 1999-08-22, 2019-04-07, 2038-11-21, 2058-07-08.
Old code hardcoded `2048 + trans_week`, which silently breaks for any recording outside the post-2019 cycle and will break again at the next rollover.
Symptom
Observed in GNSSReceiver's integration test against the ION RTL-SDR sample data, recorded 2017-09-10 in Oegstgeest, NL: PVT timestamp came out as `2037-04-26T22:57:20` — exactly 1024 weeks (~19.6 years) into the future. Position and velocity were correct (the LSQ clock-bias term absorbs the offset).
Fix
Add an `approximate_year::Integer` keyword to `calc_pvt`, threaded into `get_week`. The default is `year(now(UTC))`, which is correct for live signals; archived data passes the rough year. The cycle whose absolute date is closest to the anchor wins — anything within ~9 years of the true observation date works.
```julia
Live signal — default works:
calc_pvt(states)
Post-processing 2017 data:
calc_pvt(states; approximate_year = 2017)
```
The Galileo `get_week` overload also gains the kwarg for API consistency, even though Galileo's 12-bit WN field doesn't realistically need it.
Why not infer it from the data?
Investigated. There's no field in the LNAV broadcast that resolves the cycle:
IS-GPS-200 acknowledges the ambiguity and recommends external information — exactly what `approximate_year` provides.
Test plan
🤖 Generated with Claude Code